package com.chinaums.sftp.internal.connection;


import com.chinaums.sftp.internal.constants.ConstantValue;
import com.chinaums.sftp.internal.constants.FileMsg;
import com.chinaums.sftp.utils.ExceptionUtil;
import com.jcraft.jsch.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.commons.nullanalysis.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;


/**
 * handler for sftp.
 */
public class SftpHandlerImpl implements SftpHandler {

    private static final Logger LOG = LoggerFactory.getLogger(SftpHandlerImpl.class);

    private Session session = null;

    private ChannelSftp channelSftp = null;

    private static final String DOT = ".";

    private static final String DOT_DOT = "..";

    private static final String SP = "/";

    private static final String SRC_MAIN = "src/main";

    private static final String AUTH_FAIL = "Auth fail";
    private static final String NOT_EXIST = "no such file";


    @Override
    public void loginFtpServer(SftpConnectionOptions ftpConfig) {
        try {
            JSch jsch = new JSch();

            session = jsch.getSession(ftpConfig.getUsername().orElse("root"), ftpConfig.getHost(), ftpConfig.getPort());
            session.setPassword(ftpConfig.getPassword().orElse("root"));


            if (session == null) {
                throw new RuntimeException("login failed. Please check if username and password are correct");
            }

            Properties config = new Properties();

            // SSH 公钥检查机制 no、ask、yes
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);

            session.setTimeout(ftpConfig.getTimeout());
            session.connect();

            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
        } catch (JSchException e) {
            if (null != e.getCause()) {
                String cause = e.getCause().toString();
                String unknownHostException = "java.net.UnknownHostException: " + ftpConfig.getHost();
                String illegalArgumentException = "java.lang.IllegalArgumentException: port out of range:" + ftpConfig.getPort();
                String wrongPort = "java.net.ConnectException: Connection refused";
                if (unknownHostException.equals(cause)) {
                    String message = String.format("请确认ftp服务器地址是否正确，无法连接到地址为: [%s] 的ftp服务器", ftpConfig.getHost());
                    LOG.error(message);
                    throw new RuntimeException(message, e);
                } else if (illegalArgumentException.equals(cause) || wrongPort.equals(cause)) {
                    String message = String.format("请确认连接ftp服务器端口是否正确，错误的端口: [%s] ", ftpConfig.getPort());
                    LOG.error(message);
                    throw new RuntimeException(message, e);
                } else {
                    LOG.error(ExceptionUtil.getErrorMessage(e));
                    throw new RuntimeException(e);
                }
            } else {
                if (AUTH_FAIL.equals(e.getMessage())) {
                    String message = String.format("与ftp服务器建立连接失败,请检查用户名和密码是否正确: [%s]", "message:host =" + ftpConfig.getHost() + ",username = " + ftpConfig.getUsername() + ",port =" + ftpConfig.getPort());
                    LOG.error(message);
                    throw new RuntimeException(message, e);
                } else {
                    String message = String.format("与ftp服务器建立连接失败 : [%s]", "message:host =" + ftpConfig.getHost() + ",username = " + ftpConfig.getUsername() + ",port =" + ftpConfig.getPort());
                    LOG.error(message);
                    throw new RuntimeException(message, e);
                }
            }
        }



        String path = ftpConfig.getPath();
        String okFile = path.replace(".tar.gz", ".ok");


        // 校验ok文件
        if (ftpConfig.isFileCheckOkFile()) {
            isFileExist(okFile);
        }
        // 校验数据文件
        isFileExist(path);

    }


    @Override
    public void logoutFtpServer() {
        if (channelSftp != null) {
            channelSftp.disconnect();
        }
        if (session != null) {
            session.disconnect();
        }
    }

    @Override
    public boolean isDirExist(String directoryPath) {
        try {
            SftpATTRS sftpAttrs = channelSftp.lstat(directoryPath);
            return sftpAttrs.isDir();
        } catch (SftpException e) {
            if (e.getMessage().toLowerCase().equals(NOT_EXIST)) {
                LOG.warn("{}", e.getMessage());
                return false;
            }
            String message = String.format("进入目录：[%s]时发生I/O异常,请确认与ftp服务器的连接正常", directoryPath);
            LOG.error(message);
            throw new RuntimeException(message, e);
        }
    }

    @Override
    public boolean isFileExist(String filePath) {
        boolean isExitFlag = false;
        try {
            SftpATTRS sftpAttrs = channelSftp.lstat(filePath);
            if (sftpAttrs.getSize() >= 0) {
                isExitFlag = true;
            }
        } catch (SftpException e) {
            if (e.getMessage().equalsIgnoreCase(NOT_EXIST)) {
                LOG.warn("{}", e.getMessage());
                return false;
            } else {
                String message = String.format("获取文件：[%s] 属性时发生I/O异常,请确认文件是否存在", filePath);
                LOG.error(message);
                throw new RuntimeException(message, e);
            }
        }
        return isExitFlag;
    }

    @Override
    public InputStream getInputStream(String filePath) {
        try {
            return channelSftp.get(filePath);
        } catch (SftpException e) {
            String message = String.format("读取文件 : [%s] 时出错,请确认文件：[%s]存在且配置的用户有权限读取", filePath, filePath);
            LOG.error(message);
            throw new RuntimeException(message, e);
        }
    }

    @Override
    public List<String> getFiles(String path) {
        List<FileMsg> filesByPath = getFilesByPath(new FileMsg(path));
        return filesByPath.stream().sorted(Comparator.comparing(FileMsg::getmTime)).map(msg -> {
            String filePath = msg.getFilePath();
            long fileSize = msg.getFileSize();
            if (fileSize != 0) {
                return filePath + ConstantValue.FILE_REGEX_SPLIT + fileSize;
            } else {
                return filePath;
            }
        }).collect(Collectors.toList());
    }

    private List<FileMsg> getFilesByPath(FileMsg fileMsg) {
        if (StringUtils.isBlank(fileMsg.getFilePath())) {
            fileMsg.setFilePath(SP);
        }
        List<FileMsg> sources = new ArrayList<>();
        String path = fileMsg.getFilePath();
        if (isDirExist(fileMsg.getFilePath())) {
            if (path.equals(DOT) || path.equals(SRC_MAIN)) {
                return sources;
            }
            if (!path.endsWith(SP)) {
                path = path + SP;
                fileMsg.setFilePath(path);
            }
            try {
                Vector vector = channelSftp.ls(path);
                for (int i = 0; i < vector.size(); ++i) {
                    ChannelSftp.LsEntry le = (ChannelSftp.LsEntry) vector.get(i);
                    String strName = le.getFilename();
                    if (!strName.equals(DOT) && !strName.equals(SRC_MAIN) && !strName.equals(DOT_DOT)) {
                        FileMsg file;
                        String filePath = path + strName;
                        SftpATTRS attrs = le.getAttrs();
                        int mTime = attrs.getMTime();
                        file = new FileMsg(filePath, mTime);
                        sources.addAll(getFilesByPath(file));
                    }
                }
            } catch (SftpException e) {
                LOG.error("", e);
            }
        } else if (isFileExist(fileMsg.getFilePath())) {
            sources.add(fileMsg);
            return sources;
        }
        return sources;
    }


    @Override
    public void deleteAllFilesInDir(String dir, List<String> exclude) {
        if (isDirExist(dir)) {
            if (!dir.endsWith(SP)) {
                dir = dir + SP;
            }

            try {
                Vector vector = channelSftp.ls(dir);
                for (int i = 0; i < vector.size(); ++i) {
                    ChannelSftp.LsEntry le = (ChannelSftp.LsEntry) vector.get(i);
                    String strName = le.getFilename();
                    if (CollectionUtils.isNotEmpty(exclude) && exclude.contains(strName)) {
                        continue;
                    }

                    if (!strName.equals(DOT) && !strName.equals(SRC_MAIN) && !strName.equals(DOT_DOT)) {
                        String filePath = dir + strName;
                        deleteAllFilesInDir(filePath, exclude);
                    }
                }

                if (CollectionUtils.isEmpty(exclude)) {
                    channelSftp.rmdir(dir);
                }
            } catch (SftpException e) {
                LOG.error("", e);
                throw new RuntimeException(e);
            }
        } else if (isFileExist(dir)) {
            try {
                channelSftp.rm(dir);
            } catch (SftpException e) {
                LOG.error("", e);
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public BufferedReader getGZInputStream(String gzFilePath, @NotNull String encoding) {
        try {
            GZIPInputStream is = new GZIPInputStream(getInputStream(gzFilePath));
            return new BufferedReader(new InputStreamReader(is, encoding));
        } catch (Exception e) {
            String message = " read tar.gz file error ,Please make sure the file exists and the configured user has permission to read it";
            LOG.error(message);
            throw new RuntimeException(message, e);
        }
    }

    // getInputStream
    @Override
    public BufferedReader getZipInputStream(String ZipFilePath,  @NotNull  String encoding)  throws IOException {
        try {
            ZipInputStream zin = new ZipInputStream(getInputStream(ZipFilePath));
            zin.getNextEntry();
            return new BufferedReader(new InputStreamReader(zin));
        } catch (Exception e) {
            String message = " read zip file error ,Please make sure the file exists and the configured user has permission to read it";
            LOG.error(message);
            throw new RuntimeException(message, e);
        }

    }
}
